package com.example.yckjbigdataservice.controller;

import com.example.yckjbigdataservice.model.MyIndexDocument;
import com.example.yckjbigdataservice.model.QueryParams;
import com.example.yckjbigdataservice.model.ScrollPageParams;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.core.CountResponse;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RequestMapping("/es")
@RestController
@Slf4j
public class ElasticsearchController {

    /*private final PreBuiltHWTransportClient esTransportClient;

    public ElasticsearchController(PreBuiltHWTransportClient esTransportClient) {
        this.esTransportClient = esTransportClient;
    }*/

    private final RestHighLevelClient restHighLevelClient;

    public ElasticsearchController(RestHighLevelClient restHighLevelClient) {
        this.restHighLevelClient = restHighLevelClient;
    }

    private final ObjectMapper objectMapper = new ObjectMapper();

    @GetMapping("/index/create")
    public String sendMessage(@RequestParam("index") String index) {

        /*ObjectMapper mapper = new ObjectMapper();
        AtomicInteger ids = new AtomicInteger(0);
        Article article = new Article(ids.getAndIncrement(), "Elasticsearch Reference",
                "Elasticsearch is a highly scalable open-source full-text search and analytics engine.",
                "https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started.html",
                Calendar.getInstance().getTime(), "https://www.gitbook.com/@imalexyang/dashboard", "Alex Yang");
        String json = null;
        try {
            json = mapper.writeValueAsString(article);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

        CreateIndexResponse response;

        // 2. 定义索引配置（分片、副本、压缩）
        Settings indexSettings = Settings.builder()
                .put("index.number_of_shards", 3)                   // 分片数
                .put("index.number_of_replicas", 1)                 // 副本数
                .put("index.codec", "best_compression")             // 启用 Lucene 压缩
                .build();

        // 3. 创建索引请求
        CreateIndexRequest request = new CreateIndexRequest(index)
                .settings(indexSettings)
                .mapping(json, XContentType.JSON);
        try {
            // 类型：https://blog.csdn.net/Mr_Air_Boy/article/details/122412269
            //esTransportClient.prepare().prepareGet(index, type, "id").get();
            //response = esTransportClient.prepare().prepareIndex(index, type).setSource(json, XContentType.JSON).get();
            // 4. 执行创建
            response = esTransportClient.admin().indices().create(request).actionGet();
            //CreateIndexResponse response = client.admin().indices().create(request).actionGet();
        } catch (ElasticsearchSecurityException e) {
            log.error("es创建出错",e);
        }*/

        GetIndexRequest isExistsRequest = new GetIndexRequest(index);
        try {
            boolean existsIndex = restHighLevelClient.indices().exists(isExistsRequest, RequestOptions.DEFAULT);
            if (!existsIndex) { // 不存在就创建
                log.info("不存在创建 index [{}] failed", index);
                // 创建索引请求
                CreateIndexRequest request = new CreateIndexRequest(index);
                // 设置分片、副本、压缩等参数
                request.settings(Settings.builder()
                        .put("index.number_of_shards", 3)
                        .put("index.number_of_replicas", 1)
                        .put("index.codec", "best_compression")
                        .put("index.refresh_interval", "10s")
                );

                // 设置 mapping（字段类型和分词器）
                ObjectMapper mapper = new ObjectMapper();
                // 1. 构造 title 字段 mapping
                Map<String, Object> titleField = new HashMap<>();
                titleField.put("type", "text");
                titleField.put("analyzer", "ik_max_word");
                titleField.put("search_analyzer", "ik_smart");

                // 2. 构造 author 字段 mapping
                Map<String, Object> authorField = new HashMap<>();
                authorField.put("type", "keyword");

                // 3. 构造 publish_time 字段 mapping
                Map<String, Object> publishTimeField = new HashMap<>();
                publishTimeField.put("type", "date");
                publishTimeField.put("format", "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis");

                // 4. 汇总 properties
                Map<String, Object> properties = new HashMap<>();
                properties.put("title", titleField);
                properties.put("author", authorField);
                properties.put("publish_time", publishTimeField);

                // 5. 构建最终 mapping JSON
                Map<String, Object> mapping = new HashMap<>();
                mapping.put("properties", properties);

                // 6. 转为 JSON 字符串
                String mappingJson = mapper.writeValueAsString(mapping);

                request.mapping(mappingJson, XContentType.JSON);

                // 创建索引
                CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
                System.out.println("索引创建成功: " + response.isAcknowledged());
            } else {
                return "已经存在索引";
            }
        } catch (IOException e) {
            log.error("Judge index exist {} failed", index, e);
            return "创建错误";
        }
        return "OK";
    }

    @PostMapping("/index/insert") //doc.setPublish_time("2024-01-01 10:00:00");
    public String addData(@RequestParam("index") String index, @RequestBody MyIndexDocument doc) {
        String id = null;
        try {
            id = addDocument(index, doc, null);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return id+"OK";
    }

    @GetMapping("/index/query")
    public List<MyIndexDocument> queryByEntity(@RequestParam("index") String index) {
        return simpleQuery(index);
    }


    public List<MyIndexDocument> simpleQuery(String index){
        SearchRequest request = new SearchRequest(index);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        // 结合 terminate_after 限制扫描文档数
        //sourceBuilder.terminateAfter(1000); // 最多扫描 1000 条后就停止（能显著减少消耗）

        // 只返回你关心的字段（减少 payload）
        sourceBuilder.fetchSource(new String[] {"id", "title"}, null); // 只返回 id 和

        // 关闭高亮 / 评分 / 解释（如不需要）
        sourceBuilder.explain(false);
        sourceBuilder.trackScores(false); // 不需要排序打分时关闭

        // 分页式返回时使用 search_after 替代 from
        // sourceBuilder.from(10000);  // ❌ 慢，不推荐
        // 改为： sourceBuilder.searchAfter(new Object[]{"lastValue", "lastId"});

        //sourceBuilder.query(QueryBuilders.matchQuery("title", "Kafka 教程"));
        sourceBuilder.size(20); // 限制最多返回 20 条

        // 限定时间范围、状态条件
        /*BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.filter(QueryBuilders.rangeQuery("timestamp").gte("2025-01-01"));
        boolQuery.filter(QueryBuilders.termQuery("status", "active"));
        sourceBuilder.query(boolQuery);*/

        // 设置 Timeout 防止查询拖垮集群
        sourceBuilder.timeout(TimeValue.timeValueSeconds(3)); // 最多执行 3 秒

        request.source(sourceBuilder);

        List<MyIndexDocument> results = null;
        try {
            SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

            results = new ArrayList<>();
            for (SearchHit hit : response.getHits()) {
                MyIndexDocument doc = objectMapper.readValue(hit.getSourceAsString(), MyIndexDocument.class);
                results.add(doc);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return results;

    }

    // 查询 + 分页 + 返回实体类
    public List<MyIndexDocument> searchByConditions(String index, String title, String author, int page, int size) throws IOException {
        SearchRequest request = new SearchRequest(index);

        // 构建查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if (title != null && !title.isEmpty()) {
            boolQuery.must(QueryBuilders.matchQuery("title", title));
        }
        if (author != null && !author.isEmpty()) {
            boolQuery.must(QueryBuilders.termQuery("author", author));
        }

        // 分页设置
        int from = (page - 1) * size;
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
                .query(boolQuery)
                .from(from)
                .size(size);

        request.source(sourceBuilder);

        // 执行查询
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        List<MyIndexDocument> results = new ArrayList<>();
        for (SearchHit hit : response.getHits()) {
            MyIndexDocument doc = objectMapper.readValue(hit.getSourceAsString(), MyIndexDocument.class);
            results.add(doc);
        }

        return results;
    }


    // 查询 + 分页 + 返回 Map
    public List<Map<String, Object>> searchAsMap(String index, String title, String author, int page, int size) throws IOException {
        SearchRequest request = new SearchRequest(index);

        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if (title != null && !title.isEmpty()) {
            boolQuery.must(QueryBuilders.matchQuery("title", title));
        }
        if (author != null && !author.isEmpty()) {
            boolQuery.must(QueryBuilders.termQuery("author", author));
        }

        int from = (page - 1) * size;
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
                .query(boolQuery)
                .from(from)
                .size(size);

        request.source(sourceBuilder);

        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        List<Map<String, Object>> results = new ArrayList<>();
        for (SearchHit hit : response.getHits()) {
            results.add(hit.getSourceAsMap());
        }

        return results;
    }

    // 新增文档（自动生成 ID 或手动指定）doc.setPublish_time("2024-01-01 10:00:00");
    public String addDocument(String index, MyIndexDocument doc, String id) throws IOException {
        IndexRequest request = new IndexRequest(index);

        if (id != null) {
            request.id(id); // 手动指定 ID
        }

        String json = objectMapper.writeValueAsString(doc);
        request.source(json, XContentType.JSON);

        IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
        return response.getId(); // 返回文档 ID
    }

    // 更新文档（根据 ID 修改部分字段）[更新是部分更新（非全量替换），只修改你传入的字段。]
    public void updateDocument(String index,String id, Map<String, Object> updatedFields) throws IOException {
        UpdateRequest request = new UpdateRequest(index, id)
                .doc(updatedFields); // 只传需要更新的字段
        UpdateResponse response = restHighLevelClient.update(request, RequestOptions.DEFAULT);
        System.out.println("更新完成: " + response.getResult());
    }

    // 删除文档（根据 ID）
    public void deleteDocument(String index, String id) throws IOException {
        DeleteRequest request = new DeleteRequest(index, id);
        DeleteResponse response = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
        System.out.println("删除结果: " + response.getResult());
    }


    /**
     * 复杂查询实现方法（返回实体）
     *
     * QueryParams params = new QueryParams();
     * params.setTitleKeyword("Elasticsearch");
     * params.setAuthors(Arrays.asList("zhangsan", "lisi")); // author=zhangsan OR lisi
     * params.setPublishTimeFrom("2024-01-01 00:00:00");
     * params.setPublishTimeTo("2025-01-01 00:00:00");
     * params.setPage(1);
     * params.setSize(5);
     * params.setSortField("publish_time");
     * params.setAsc(false);
     *
     * List<MyIndexDocument> result = complexSearch(params);
     *
     * @param index
     * @param params
     * @return
     * @throws IOException
     */
    public List<MyIndexDocument> complexSearch(String index, QueryParams params) throws IOException {
        SearchRequest request = new SearchRequest(index);
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        // 1. 模糊匹配 title
        if (params.getTitleKeyword() != null) {
            boolQuery.must(QueryBuilders.matchQuery("title", params.getTitleKeyword()));
        }

        // 2. 精确匹配 title
        if (params.getExactTitle() != null) {
            boolQuery.must(QueryBuilders.termQuery("title.keyword", params.getExactTitle()));
        }

        // 3. authors OR 匹配
        if (params.getAuthors() != null && !params.getAuthors().isEmpty()) {
            BoolQueryBuilder authorShould = QueryBuilders.boolQuery();
            for (String author : params.getAuthors()) {
                authorShould.should(QueryBuilders.termQuery("author.keyword", author));
            }
            authorShould.minimumShouldMatch(1); // 至少匹配一个
            boolQuery.must(authorShould);
        }

        // 4. 时间范围查询
        if (params.getPublishTimeFrom() != null || params.getPublishTimeTo() != null) {
            RangeQueryBuilder range = QueryBuilders.rangeQuery("publish_time");
            if (params.getPublishTimeFrom() != null) {
                range.gte(params.getPublishTimeFrom());
            }
            if (params.getPublishTimeTo() != null) {
                range.lte(params.getPublishTimeTo());
            }
            boolQuery.must(range);
        }

        // 5. 构建分页 + 排序
        int from = (params.getPage() - 1) * params.getSize();
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
                .query(boolQuery)
                .from(from)
                .size(params.getSize())
                .sort(params.getSortField(), params.isAsc() ? SortOrder.ASC : SortOrder.DESC);

        request.source(sourceBuilder);

        // 执行查询
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        List<MyIndexDocument> results = new ArrayList<>();
        for (SearchHit hit : response.getHits()) {
            results.add(objectMapper.readValue(hit.getSourceAsString(), MyIndexDocument.class));
        }

        return results;
    }


    /**
     * 创建索引 + 映射 + 设置别名
     *
     * 写入时使用别名
     * IndexRequest request = new IndexRequest("logs-write")
     *     .source(Map.of("message", "系统启动", "level", "INFO", "timestamp", "2025-05-30 10:10:10"), XContentType.JSON);
     * client.index(request, RequestOptions.DEFAULT);
     *
     * @param baseIndexName
     * @param date
     * @throws IOException
     */
    public void createMonthlyIndex(String baseIndexName, LocalDate date) throws IOException {
        String monthStr = date.format(DateTimeFormatter.ofPattern("yyyy-MM"));
        String indexName = baseIndexName + "-" + monthStr; // logs-2025-05

        // 1. 设置分片、副本、压缩等
        Settings settings = Settings.builder()
                .put("index.number_of_shards", 3)
                .put("index.number_of_replicas", 1)
                .put("index.codec", "best_compression") // 压缩
                .build();

        // 2. Mapping
        Map<String, Object> mapping = new HashMap<>();
        Map<String, Object> properties = new HashMap<>();

        /*properties.put("message", Map.of("type", "text", "analyzer", "standard"));
        properties.put("level", Map.of("type", "keyword"));
        properties.put("timestamp", Map.of("type", "date", "format", "yyyy-MM-dd HH:mm:ss"));*/

        mapping.put("properties", properties);

        // 3. 创建请求
        CreateIndexRequest request = new CreateIndexRequest(indexName)
                .settings(settings)
                .mapping(mapping)
                .alias(new Alias(baseIndexName)) // 查询别名，如 logs
                .alias(new Alias(baseIndexName + "-write").writeIndex(true)); // 写入别名 logs-write

        if (!restHighLevelClient.indices().exists(new GetIndexRequest(indexName), RequestOptions.DEFAULT)) {
            restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
            System.out.println("索引创建成功: " + indexName);
        } else {
            System.out.println("索引已存在: " + indexName);
        }
    }


    /**
     * 在 Elasticsearch 中，当你需要进行 深度分页（如翻到几百页之后）时，传统的 from + size 性能会严重下降，因为它需要跳过大量文档。
     *
     * 这时应该使用 search_after，它的性能更优，不受 from 影响，适合 基于排序字段的“游标式分页”。
     *
     *
     *
     * 获取下一页的 searchAfter 值
     * SearchHit[] hits = response.getHits().getHits();
     * if (hits.length > 0) {
     *     Object[] nextSearchAfter = hits[hits.length - 1].getSortValues();
     *     // 保存 nextSearchAfter 到下一次请求中
     * }
     *
     *
     *
     * 示例调用
     * ScrollPageParams params = new ScrollPageParams();
     * params.setTitleKeyword("kafka");
     * params.setPageSize(10);
     * params.setSearchAfter(Arrays.asList("2025-05-01 10:00:00", "doc123")); // 来自上一页的最后一条
     *
     * List<MyIndexDocument> results = searchAfterQuery(params);
     *
     *
     *
     * @param index
     * @param params
     * @return
     * @throws IOException
     */

    public List<MyIndexDocument> searchAfterQuery(String index, ScrollPageParams params) throws IOException {
        SearchRequest request = new SearchRequest(index);

        // 构建 query
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if (params.getTitleKeyword() != null) {
            boolQuery.must(QueryBuilders.matchQuery("title", params.getTitleKeyword()));
        }
        if (params.getPublishTimeAfter() != null) {
            boolQuery.must(QueryBuilders.rangeQuery("publish_time").gte(params.getPublishTimeAfter()));
        }

        // 排序字段必须唯一 & 稳定
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
                .query(boolQuery)
                .size(params.getPageSize())
                .sort("publish_time", SortOrder.DESC)
                .sort("_id", SortOrder.ASC); // 保证唯一性

        // 设置 search_after
        if (params.getSearchAfter() != null) {
            sourceBuilder.searchAfter(params.getSearchAfter().toArray());
        }

        request.source(sourceBuilder);
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        List<MyIndexDocument> resultList = new ArrayList<>();
        for (SearchHit hit : response.getHits().getHits()) {
            resultList.add(objectMapper.readValue(hit.getSourceAsString(), MyIndexDocument.class));
        }

        return resultList;
    }


    private void count(){
        CountRequest countRequest = new CountRequest("my-index");

        // 可选：添加查询条件（如只统计 status = "ok"）
        BoolQueryBuilder query = QueryBuilders.boolQuery()
                .must(QueryBuilders.termQuery("status", "ok"));
        countRequest.query(query);

        /*RangeQueryBuilder timeRange = QueryBuilders.rangeQuery("timestamp")
                .gte("2025-01-01 00:00:00")
                .lte("2025-05-30 23:59:59");
        countRequest.query(timeRange);*/

        CountResponse response = null;
        try {
            response = restHighLevelClient.count(countRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        long total = response.getCount();

        System.out.println("文档总数: " + total);
    }
}
